package com.wanji.gateway.protocol;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 基于文件签名的动态类加载器，支持增量加载
 */
public class SignatureBasedClassLoader extends URLClassLoader {
    private static final Logger log = LoggerFactory.getLogger(SignatureBasedClassLoader.class);

    // 文件路径到签名的映射
    private final Map<String, String> fileSignatures = new ConcurrentHashMap<>();

    // 已加载的JAR文件
    private final List<File> loadedJars = new ArrayList<>();

    public SignatureBasedClassLoader(String sdkDir, ClassLoader parent) throws IOException {
        super(new URL[0], parent);
        loadJarsFromDirectory(sdkDir, true);
    }

    /**
     * 从目录加载JAR文件，支持全量或增量加载
     */
    public List<File> loadJarsFromDirectory(String sdkDir, boolean fullScan) throws IOException {
        File dir = new File(sdkDir);
        if (!dir.exists() || !dir.isDirectory()) {
            log.warn("SDK目录不存在或不是目录: {}", sdkDir);
            return Collections.emptyList();
        }

        log.info("开始扫描SDK目录: {}", sdkDir);
        List<File> newJars = new ArrayList<>();

        // 获取目录下所有JAR文件
        File[] files = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".jar"));
        if (files == null) {
            return Collections.emptyList();
        }

        for (File jarFile : files) {
            String filePath = jarFile.getAbsolutePath();
            String currentSignature = calculateFileSignature(jarFile);

            if (currentSignature == null) {
                log.warn("无法计算文件签名: {}", filePath);
                continue;
            }

            // 检查文件是否新增或修改
            if (fullScan || !fileSignatures.containsKey(filePath) ||
                    !fileSignatures.get(filePath).equals(currentSignature)) {

                // 新增或修改的文件需要重新加载
                if (loadedJars.contains(jarFile)) {
                    // 文件已加载但签名不同，需要重新加载
                    log.info("检测到文件变更，将重新加载: {}", filePath);
                    removeJar(jarFile);
                } else {
                    log.info("发现新JAR文件: {}", filePath);
                }

                // 添加新JAR到类加载器
                addURL(jarFile.toURI().toURL());
                loadedJars.add(jarFile);
                fileSignatures.put(filePath, currentSignature);
                newJars.add(jarFile);

                log.info("已加载JAR文件: {} (签名: {})", filePath, currentSignature.substring(0, 8));
            }
        }

        // 移除已删除的文件
        if (fullScan) {
            Iterator<Map.Entry<String, String>> it = fileSignatures.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, String> entry = it.next();
                File file = new File(entry.getKey());
                if (!file.exists()) {
                    log.info("检测到文件已删除: {}", entry.getKey());
                    it.remove();

                    // 从已加载列表中移除
                    loadedJars.removeIf(f -> f.getAbsolutePath().equals(entry.getKey()));
                }
            }
        }

        log.info("扫描完成，新增/修改 {} 个JAR文件", newJars.size());
        return newJars;
    }

    /**
     * 计算文件的SHA-1签名
     */
    private String calculateFileSignature(File file) {
        try (InputStream is = Files.newInputStream(file.toPath())) {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            byte[] buffer = new byte[8192];
            int bytesRead;

            while ((bytesRead = is.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }

            byte[] hash = digest.digest();
            StringBuilder hexString = new StringBuilder();

            for (byte b : hash) {
                String hex = String.format("%02x", b);
                hexString.append(hex);
            }

            return hexString.toString();
        } catch (NoSuchAlgorithmException | IOException e) {
            log.error("计算文件签名失败: {}", file.getAbsolutePath(), e);
            return null;
        }
    }

    /**
     * 从类加载器中移除JAR文件
     */
    private void removeJar(File jarFile) {
        // 由于URLClassLoader不支持直接移除URL，这里只能记录
        log.info("标记JAR文件为需要重新加载: {}", jarFile.getAbsolutePath());

        // 在实际应用中，可能需要创建新的类加载器实例
        // 这里简化处理，只记录状态，下次全量加载时会重新处理
    }

    /**
     * 获取文件签名
     */
    public String getFileSignature(String filePath) {
        return fileSignatures.get(filePath);
    }

    /**
     * 获取所有已加载的JAR文件
     */
    public List<File> getLoadedJars() {
        return new ArrayList<>(loadedJars);
    }

    /**
     * 扫描JAR包中的指定类型
     */
    public <T> List<Class<? extends T>> scanForType(String jarPath, Class<T> type) {
        List<Class<? extends T>> result = new ArrayList<>();
        File jarFile = new File(jarPath);

        if (!jarFile.exists()) {
            log.warn("JAR文件不存在: {}", jarPath);
            return result;
        }

        try (JarFile jar = new JarFile(jarFile)) {
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String className = entry.getName();

                // 只处理类文件
                if (className.endsWith(".class") && !entry.isDirectory()) {
                    className = className.replace('/', '.').substring(0, className.length() - 6);

                    try {
                        Class<?> clazz = loadClass(className);
                        if (type.isAssignableFrom(clazz) && !clazz.equals(type)) {
                            @SuppressWarnings("unchecked")
                            Class<? extends T> implClass = (Class<? extends T>) clazz;
                            result.add(implClass);
                            log.info("发现实现类: {}", className);
                        }
                    } catch (Throwable t) {
                        log.debug("加载类失败: {}", className, t);
                    }
                }
            }
        } catch (IOException e) {
            log.error("扫描JAR包失败: {}", jarPath, e);
        }

        return result;
    }
}
